package com.cuz.hot100;

/**
 * 基于synchronized + Object.wait() + Object.notifyAll()的等待唤醒机制
 * 实现 :
 * 开启个 threadNum 线程， 这些线程的 ID 分别是 A、B, C...
 * 每个线程把自己的 ID 在屏幕上打印 eachThreadPrintTimes遍
 *
 * 使用用例如下：
 * <pre> {@code
 *      public static void main(String[] args) {
 *         ThreadPrinter threadPrinter = new ThreadPrinter(3, 10);
 *         threadPrinter.runAllPrintThread();
 *     }
 * }</pre>
 * @author 陈兴
 * @version 1.0
 **/
public class ThreadPrinter {

    public static void main(String[] args) {
        ThreadPrinter threadPrinter = new ThreadPrinter(3, 10);
        threadPrinter.runAllPrintThread();
    }

    /**
     * 线程数目
     */
    private final int threadNum;

    /***
     * 每一个 线程需要打印多少次
     */
    private final int eachThreadPrintTimes;
    /**
     * 计数
     */
    private int count = 0;

    /***
     * @param threadNum 启动多少个线程
     * @param eachThreadPrintTimes 每一个线程打印多少次
     */
    public ThreadPrinter(int threadNum, int eachThreadPrintTimes) {
        if (threadNum <= 0) {
            throw new IllegalArgumentException("线程数需要大于0");
        }
        if (eachThreadPrintTimes <= 0) {
            throw new IllegalArgumentException("每一个线程打印次数需要大于0");
        }
        this.threadNum = threadNum;
        this.eachThreadPrintTimes = eachThreadPrintTimes;
    }

    /***
     * 开启 threadNum 个PrintThread线程
     * 并让PrintThread们启动运行
     */
    public void runAllPrintThread() {

        for (int seq = 0; seq < threadNum; seq++) {
            String threadName = generateThreadName(seq);
            new PrintThread(seq, this.eachThreadPrintTimes, threadName).start();
        }

    }

    /**
     * 让线程打印
     *
     * 基于synchronized + Object.wait() + Object.notifyAll()的等待唤醒机制
     * 打印时机：计数%线程总数 = 打印线程的id 否则进入wait
     *
     * 打印操作：打印线程的名称，并让 计数count++ 并让PrintThread需要打印的次数减小
     *
     * 如果打印线程被中断 那么直接返回
     * @param thread 打印线程
     */
    private void runPrintThread(PrintThread thread) {

        //打印线程还需要继续打印么
        while (thread.shouldContinue()) {
            synchronized (this) {

                //条件 满足才可以打印 否则进入等待
                while (count % threadNum != thread.getPrintId()) {
                    try {
                        //等待
                        this.wait();
                    } catch (InterruptedException ignore) {
                        //当前线程被中断 那么 结束
                        return;
                    }
                }
                //计数++
                count++;
                //线程需要打印次数减小
                thread.decreaseNeedPrintTimes();
                //打印
                thread.print();
                //唤醒其他线程
                this.notifyAll();
            }
        }
    }


    /***
     * 生成线程名称
     * @param seq 线程需要
     * @return 线程名称
     */
    private String generateThreadName(int seq) {
        return Character.toString((char) ('A' + seq));
    }

    /**
     * 打印线程 内部类
     */
    class PrintThread extends Thread {
        /**
         * 线程打印id
         */
        private final int printId;
        /**
         * 需要打印的次数
         */
        private int needPrintTimes;

        PrintThread(int printId, int needPrintTimes, String threadName) {
            this.printId = printId;
            this.needPrintTimes = needPrintTimes;
            this.setName(threadName);
        }

        /**
         * @return 打印id
         */
        public int getPrintId() {
            return printId;
        }

        /***
         * 减少需要打印的次数
         */
        public void decreaseNeedPrintTimes() {
            this.needPrintTimes--;
        }

        /**
         * needPrintTimes>0   ==> 需要继续打印
         * @return  是否需要继续打印
         */
        public boolean shouldContinue() {
            return needPrintTimes > 0;
        }

        /***
         * 打印当前线程名称
         */
        public void print() {
            System.out.println(this.getName());
        }

        @Override
        public void run() {
            runPrintThread(this);
        }
    }


}
